package com.milhouz.wormdroid.gl.view;

import java.util.concurrent.Semaphore;

import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.opengl.GLSurfaceView.GLWrapper;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import com.milhouz.wormdroid.data.model.GameData;
import com.milhouz.wormdroid.gl.model.BotGLSprite;
import com.milhouz.wormdroid.gl.model.hud.IHudSprite;
import com.milhouz.wormdroid.gl.renderer.EglHelper;
import com.milhouz.wormdroid.gl.renderer.Renderer;
import com.milhouz.wormdroid.util.ProfileRecorder;

/**
 * An implementation of SurfaceView that uses the dedicated surface for displaying an OpenGL animation. <br>
 * This allows the animation to run in a separate thread, without requiring that it be driven by the update mechanism of the view hierarchy. The
 * application-specific rendering code is delegated to a GLView.Renderer instance.
 */
public class GameGLSurfaceView extends SurfaceView implements SurfaceHolder.Callback {

	// GAME DATA
	private GameData mGameData;

	// SCREEN VIEW DATA
	private int xval;
	private int yval;
	private int zx;
	private int zy;
	public boolean isViewMoved = false;

	public GameGLSurfaceView(Context context) {
		super(context);
		init();
	}

	private void init() {
		// Install a SurfaceHolder.Callback so we get notified when the underlying surface is created and destroyed
		mHolder = getHolder();
		mHolder.addCallback(this);
		mHolder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
		setFocusableInTouchMode(true);
	}

	public void setRenderer(Renderer renderer) {
		mGLThread = new GLThread(renderer);
		mGLThread.start();
	}

	/** Display the weapon dialog window in order to choose a weapon */
	public void onSelectMenuWeapon() {
		DialogTestView dialog = new DialogTestView(getContext());
		dialog.show();
	}

	/** Change the type of camera used in the game */
	public void onSelectCameraView() {
		if (getSelectedBot().isViewCentered) {
			isViewMoved = true;
			getSelectedBot().isViewCentered = false;
		} else {
			mGLThread.mRenderer.centerCameraOnSprite(getSelectedBot());
			isViewMoved = false;
			getSelectedBot().isViewCentered = true;
		}
	}

	/** Change the level of zoom used in the game */
	public void onSelectMenuZoom() {
		mGLThread.mRenderer.setZoom();
	}

	/** Key events for game actions */
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent msg) {
		if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
			getSelectedBot().moveRight();
		}
		if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
			getSelectedBot().moveLeft();
		}
		if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
			getSelectedBot().jump();
		}
		return super.onKeyDown(keyCode, msg);
	}

	/** Trackball events for bot moves */
	@Override
	public boolean onTrackballEvent(MotionEvent event) {
		int action = event.getAction();
		if (action == MotionEvent.ACTION_MOVE) {
			if (event.getY() < 0) {
				// Up
				getSelectedBot().jump();
				return true;
			} else if (event.getY() > 0) {
				// Down
			} else if (event.getX() >= 0) {
				// Right
				getSelectedBot().moveRight();
				return true;
			} else if (event.getX() < 0) {
				// Left
				getSelectedBot().moveLeft();
				return true;
			}
		}
		return super.onTrackballEvent(event);
	}

	/** Touch events for camera moves */
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		if (!getSelectedBot().isViewCentered) {
			int action = event.getAction();
			if (action == MotionEvent.ACTION_DOWN) {
				xval = (int) event.getX();
				yval = (int) event.getY();
				isViewMoved = true;
			}
			if (action == MotionEvent.ACTION_UP) {
				if (event.getX() < 20 && event.getY() < 20) {
					zx = 0;
					zy = 0;
					isViewMoved = true;
				}
			}
			if (action == MotionEvent.ACTION_MOVE) {
				zx = zx + ((int) event.getX() - xval);
				zy = zy + ((int) event.getY() - yval);
				xval = (int) event.getX();
				yval = (int) event.getY();
				isViewMoved = true;
			}
		}
		return isViewMoved;
	}

	public void surfaceCreated(SurfaceHolder holder) {
		mGLThread.surfaceCreated();
	}

	public void surfaceDestroyed(SurfaceHolder holder) {
		// Surface will be destroyed when we return
		mGLThread.surfaceDestroyed();
	}

	public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
		// Surface size or format has changed.
		mGLThread.onWindowResize(w, h);
	}

	/**
	 * Inform the view that the activity is paused.
	 */
	public void onPause() {
		mGLThread.onPause();
	}

	/**
	 * Inform the view that the activity is resumed.
	 */
	public void onResume() {
		mGLThread.onResume();
	}

	/**
	 * Inform the view that the window focus has changed.
	 */
	@Override
	public void onWindowFocusChanged(boolean hasFocus) {
		super.onWindowFocusChanged(hasFocus);
		mGLThread.onWindowFocusChanged(hasFocus);
	}

	@Override
	protected void onDetachedFromWindow() {
		super.onDetachedFromWindow();
		mGLThread.requestExitAndWait();
	}

	// ----------------------------------------------------------------------

	public void setGameData(GameData gameData) {
		this.mGameData = gameData;
	}

	public GameData getGameData() {
		return mGameData;
	}

	public BotGLSprite getSelectedBot() {
		BotGLSprite selBot = (BotGLSprite) mGLThread.mRenderer.getSpriteById(mGameData.currentBotId);
		selBot.isSelectedBot = true;
		return selBot;
	}

	// ----------------------------------------------------------------------

	/**
	 * A generic GL Thread. Takes care of initializing EGL and GL. Delegates to a Renderer instance to do the actual drawing.
	 * 
	 */

	class GLThread extends Thread {
		GLThread(Renderer renderer) {
			super();
			mDone = false;
			mWidth = 0;
			mHeight = 0;
			mRenderer = renderer;
			setName("GLThread");
		}

		@Override
		public void run() {
			/*
			 * When the android framework launches a second instance of an activity, the new instance's onCreate() method may be called before the
			 * first instance returns from onDestroy().
			 * 
			 * This semaphore ensures that only one instance at a time accesses EGL.
			 */
			try {
				try {
					sEglSemaphore.acquire();
				} catch (InterruptedException e) {
					return;
				}
				guardedRun();
			} catch (InterruptedException e) {
				// fall thru and exit normally
			} finally {
				sEglSemaphore.release();
			}
		}

		private void guardedRun() throws InterruptedException {
			mEglHelper = new EglHelper();
			/*
			 * Specify a configuration for our opengl session and grab the first configuration that matches is
			 */
			int[] configSpec = mRenderer.getConfigSpec();
			mEglHelper.start(configSpec);

			GL10 gl = null;
			boolean tellRendererSurfaceCreated = true;
			boolean tellRendererSurfaceChanged = true;

			/*
			 * This is our main activity thread's loop, we go until asked to quit.
			 */
			while (!mDone) {
				ProfileRecorder.sSingleton.start(ProfileRecorder.PROFILE_FRAME);
				/*
				 * Update the asynchronous state (window size)
				 */
				int w, h;
				boolean changed;
				boolean needStart = false;
				synchronized (this) {
					if (mPaused) {
						mEglHelper.finish();
						needStart = true;
					}
					if (needToWait()) {
						while (needToWait()) {
							wait();
						}
					}
					if (mDone) {
						break;
					}
					changed = mSizeChanged;
					w = mWidth;
					h = mHeight;
					mSizeChanged = false;
				}
				if (needStart) {
					mEglHelper.start(configSpec);
					tellRendererSurfaceCreated = true;
					changed = true;
				}
				if (changed) {
					gl = (GL10) mEglHelper.createSurface(mHolder, mGLWrapper);
					tellRendererSurfaceChanged = true;
				}
				if (tellRendererSurfaceCreated) {
					mRenderer.surfaceCreated(gl);
					tellRendererSurfaceCreated = false;
				}
				if (tellRendererSurfaceChanged) {
					mRenderer.sizeChanged(gl, w, h);
					tellRendererSurfaceChanged = false;
				}
				if ((w > 0) && (h > 0)) {
					ProfileRecorder.sSingleton.start(ProfileRecorder.PROFILE_DRAW);
					// Round time over
					if (mGameData.isEndOfRound()) {
						// Change selected bot
						// getSelectedBot().isViewCentered = false;
						// mGameData.setSelectedBotHud(((BotGLSprite) mRenderer.getSpriteById(mGameData.getNextBotId())));
					}
					// Translate the view when screen is touch
					if (isViewMoved) {
						mRenderer.moveCamera(zx, zy);
						isViewMoved = false;
					} else {
						// Center the camera to the current sprite
						mRenderer.moveCameraWithSprite(getSelectedBot());
					}

					/* draw a frame here */
					mRenderer.drawFrame(gl, w, h);
					ProfileRecorder.sSingleton.stop(ProfileRecorder.PROFILE_DRAW);
					/*
					 * Once we're done with GL, we need to call swapBuffers() to instruct the system to display the rendered frame
					 */
					ProfileRecorder.sSingleton.start(ProfileRecorder.PROFILE_PAGE_FLIP);
					mEglHelper.swap();
					ProfileRecorder.sSingleton.stop(ProfileRecorder.PROFILE_PAGE_FLIP);
				}
				ProfileRecorder.sSingleton.stop(ProfileRecorder.PROFILE_FRAME);
				// Set the FPS value to the HUD
				if (mGameData != null)
					mGameData.mHud.setHudValue(IHudSprite.FPS_SPRITE, String.valueOf(ProfileRecorder.sSingleton
							.getAverageTime(ProfileRecorder.PROFILE_FRAME)));
				ProfileRecorder.sSingleton.endFrame();
			}

			/*
			 * clean-up everything...
			 */
			if (gl != null) {
				mRenderer.shutdown(gl);
			}

			mEglHelper.finish();
		}

		private boolean needToWait() {
			return (mPaused || (!mHasFocus) || (!mHasSurface) || mContextLost) && (!mDone);
		}

		public void surfaceCreated() {
			synchronized (this) {
				mHasSurface = true;
				mContextLost = false;
				notify();
			}
		}

		public void surfaceDestroyed() {
			synchronized (this) {
				mHasSurface = false;
				notify();
			}
		}

		public void onPause() {
			synchronized (this) {
				mPaused = true;
			}
		}

		public void onResume() {
			synchronized (this) {
				mPaused = false;
				notify();
			}
		}

		public void onWindowFocusChanged(boolean hasFocus) {
			synchronized (this) {
				mHasFocus = hasFocus;
				if (mHasFocus == true) {
					notify();
				}
			}
		}

		public void onWindowResize(int w, int h) {
			synchronized (this) {
				mWidth = w;
				mHeight = h;
				mSizeChanged = true;
			}
		}

		public void requestExitAndWait() {
			// don't call this from GLThread thread or it is a guaranteed
			// deadlock!
			synchronized (this) {
				mDone = true;
				notify();
			}
			try {
				join();
			} catch (InterruptedException ex) {
				Thread.currentThread().interrupt();
			}
		}

		private boolean mDone;
		private boolean mPaused;
		private boolean mHasFocus;
		private boolean mHasSurface;
		private boolean mContextLost;
		private int mWidth;
		private int mHeight;
		private Renderer mRenderer;
		private EglHelper mEglHelper;
	}

	private static final Semaphore sEglSemaphore = new Semaphore(1);
	private boolean mSizeChanged = true;

	private SurfaceHolder mHolder;
	private GLThread mGLThread;
	private GLWrapper mGLWrapper;

}
